const puppeteer = require("puppeteer-extra");
const StealthPlugin = require("puppeteer-extra-plugin-stealth");
puppeteer.use(StealthPlugin());

const axios = require("axios");
const fs = require("fs");
const path = require("path");
const dayjs = require("dayjs");
const readline = require("readline");
const md5 = require("md5");
const { sendWechatMessage } = require("../../utils");
const notification = require("../../utils/notification");

const { pool } = require("../../config/db");
const logger = require("../../utils/logger");
const executablePath = process.env.WEB_CONFIG_EXECUTABLE_PATH;
const userDataDir = process.env.WEB_CONFIG_USER_DATA_DIR;
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const { saveMysql } = require("../../utils/saveMysql");
const { uploadVideoToQiniu } = require("../../config/qiniu");

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});
// <------------------------------------------------------------获取用户信息和视频信息开始------------------------------------------------------------------------>
//更新数据库
async function batchInsertOrUpdateVideo(videoDataList) {
  
  if (!videoDataList.length) return;

  const querySqlTemplate = "SELECT * FROM douyin_video WHERE item_id = ?";
  const updateSqlTemplate =
    "UPDATE douyin_video SET crawl_time = ?, category = ?, cover = ?, publish_time = ?, title = ?, collect_count = ?, comment_count = ?, share_count = ?, play_count = ?, finish = ?, finish5s = ?, increase_fans_count = ?, like_count = ?, play_avg_time = ?, skip2s = ?, visit_count = ?, video_path = ? WHERE item_id = ?";
  const insertSqlTemplate =
    "INSERT INTO douyin_video (item_id, crawl_time, category, cover, publish_time, title, collect_count, comment_count, share_count, play_count, finish, finish5s, increase_fans_count, like_count, play_avg_time, skip2s, visit_count, video_path) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";

  const conn = await pool.getConnection();
  try {
    await conn.beginTransaction();
    //记录更新多少行，插入多少行
    let totalUpdatedRows = 0,
      totalInsertedRows = 0;
    for (const data of videoDataList) {
      const [queryResult] = await conn.query(querySqlTemplate, [data.item_id]);

      if (queryResult.length > 0) {
        // 如果查询结果存在，则执行更新操作
        const updateValues = [
          data.crawl_time,
          data.category,
          data.cover,
          data.publish_time,
          data.title,
          data.collect_count,
          data.comment_count,
          data.share_count,
          data.play_count,
          data.finish,
          data.finish5s,
          data.increase_fans_count,
          data.like_count,
          data.play_avg_time,
          data.skip2s,
          data.visit_count,
          data.video_path,
          data.item_id,
        ];
        const [updateResult] = await conn.query(
          updateSqlTemplate,
          updateValues
        );
        totalUpdatedRows += updateResult.affectedRows;
      } else {
        // 如果查询结果为空，则执行插入操作
        const insertValues = [
          data.item_id,
          data.crawl_time,
          data.category,
          data.cover,
          data.publish_time,
          data.title,
          data.collect_count,
          data.comment_count,
          data.share_count,
          data.play_count,
          data.finish,
          data.finish5s,
          data.increase_fans_count,
          data.like_count,
          data.play_avg_time,
          data.skip2s,
          data.visit_count,
          data.video_path,
        ];
        const [insertResult] = await conn.query(
          insertSqlTemplate,
          insertValues
        );
        totalInsertedRows += insertResult.affectedRows;
      }
    }
    await conn.commit();
    return {
      totalUpdatedRows,
      totalInsertedRows,
    };
  } catch (error) {
    await conn.rollback();
    throw error;
    logger.error(error);
  } finally {
    conn.release();
  }
}
async function getUserInfo(username) {
  logger.info(`开始获取用户 "${username}" 的信息`);
  const browser = await puppeteer.launch({
    headless: true,
    userDataDir,
    args: [
      "--no-sandbox",
      "--disable-setuid-sandbox",
      "--disable-features=site-per-process",
    ],
    ignoreHTTPSErrors: true,
  });
  const page = await browser.newPage();
  return new Promise(async (resolve, reject) => {
    let userInfo = null;
    // 监听响应事件
    page.on("response", async (response) => {
      try {
        const request = response.request();
        if (
          response
            .url()
            .includes(
              "https://www.douyin.com/aweme/v1/web/general/search/single"
            ) &&
          request.method() === "GET"
        ) {
          const body = await response.json();
          if (
            body.status_code === 0 &&
            Array.isArray(body.data) &&
            body.data.length > 0
          ) {
            const userData = body.data[0];
            if (
              userData &&
              userData.user_list &&
              userData.user_list.length > 0
            ) {
              const userInfo = userData.user_list[0].user_info;
              if (userInfo) {
                resolve({
                  name: userInfo.nickname,
                  avatar: userInfo.avatar_thumb?.url_list[0],
                  followers_count: userInfo.follower_count,
                  friends_count: userInfo.following_count,
                  url: `https://www.douyin.com/user/${userInfo.sec_uid}`,
                });
                logger.info(`${username} 用户信息获取成功`);
              } else {
                throw new Error("USER_DOES_NOT_EXIST");
              }
            } else {
              throw new Error("USER_DOES_NOT_EXIST");
            }
          } else {
            throw new Error("USER_DOES_NOT_EXIST");
          }
        }
      } catch (error) {
        reject(error);
      }
    });
    await page.goto(
      `https://www.douyin.com/search/${encodeURIComponent(username)}`,
      {
        waitUntil: "domcontentloaded",
      }
    );
  }).finally(async () => {
    await browser.close();
  });
}
async function getVideosInfo(params) {
  logger.info(`开始获取视频信息，参数：${JSON.stringify(params)}`);
  const browser = await puppeteer.launch({
    headless: false,
    userDataDir,
    executablePath,
    args: [
      "--no-sandbox",
      "--disable-setuid-sandbox",
      "--disable-features=site-per-process",
      "--start-maximized",
    ],
    ignoreHTTPSErrors: true,
  });

  try {
    const page = await browser.newPage();
    // 全屏
    const dimensions = await page.evaluate(() => ({
      width: window.innerWidth,
      height: window.innerHeight,
    }));
    await page.setViewport(dimensions);
    await page.goto("https://creator.douyin.com/");
    try {
      // #TAG: 登录按钮
      await page.waitForSelector(".douyin-creator-vmock-input-suffix", {
        timeout: 3000,
      });
      logger.error("登录已失效，正在尝试发送登录二维码...");
      await sendLoginQRCode(page);
      await waitForUserLogin(page);
    } catch (e) {
      if (e.name === "TimeoutError") {
        // 如果是超时错误，说明没有找到登录按钮，可能已经登录
        logger.info("未检测到登录按钮，用户已登录");
      } else {
        // 其他类型的错误，可能是发送二维码或登录过程中出现的问题
        logger.error(`获取视频信息过程中出现错误：${e.message}`);
        throw e; // 重新抛出错误，让上层函数处理
      }
    }
    await page.goto(
      "https://creator.douyin.com/creator-micro/data-center/content"
    );
    await page.waitForSelector("#semiTab1");
    await page.evaluate(() => {
      document.querySelector("#semiTab1").click();
    });
    await page.waitForSelector(".douyin-creator-pc-radio-addon-buttonRadio");
    //#TAG: 视频列表
    await page.evaluate(() => {
      document
        .querySelectorAll(".douyin-creator-pc-radio-addon-buttonRadio")[1]
        .click();
    });
    const result = await scrapeAllPages(page, params);

    logger.info(`爬取完成，共计${result.data.length}条数据`);
    return result;
  } catch (error) {
    logger.error("获取视频信息失败:", error);
    throw error;
  } finally {
    await browser.close();
  }
}

async function sendLoginQRCode(page) {
  try {
    await page.waitForSelector('div[class*="qrcode-image-"] img', {
      timeout: 5000,
    });

    // 检查是否存在过期的错误信息
    const isExpired = await page.evaluate(() => {
      const errorDiv = document.querySelector('div[class*="error-"]');
      return errorDiv && errorDiv.textContent.includes("点击刷新");
    });

    if (isExpired) {
      logger.info("二维码已过期，正在刷新...");
      await page.click('div[class*="qrcode-image-"] > div > img');
      await new Promise((resolve) => setTimeout(resolve, 2000)); // 等待新的二维码加载
    }

    const qrCodeBase64 = await page.evaluate(() => {
      const img = document.querySelector('div[class*="qrcode-image-"] img');
      return img ? img.src.split(",")[1] : null;
    });

    if (qrCodeBase64) {
      const imgBuffer = Buffer.from(qrCodeBase64, "base64");
      await notification(
        "抖音",
        `${process.env.NODE_ENV}环境下的抖音登录${isExpired ? "" : "过期，"
        }二维码已${isExpired ? "刷新并" : ""}发送`,
        "请扫码登录"
      );
      await sendWechatMessage({
        msgtype: "image",
        image: {
          base64: qrCodeBase64,
          md5: md5(imgBuffer),
        },
      });

      logger.info(`登录二维码已${isExpired ? "刷新并" : ""}发送到微信`);
    } else {
      logger.error("未找到登录二维码");
    }
  } catch (error) {
    logger.error(`发送登录二维码失败: ${error.message}`);
  }
}

async function waitForUserLogin(page) {
  return new Promise((resolve, reject) => {
    let qrCodeResendCount = 0;
    const maxResendCount = 50; // 最多发50次二维码

    const checkInterval = setInterval(async () => {
      try {
        await page.waitForSelector(".douyin-creator-vmock-input-suffix", {
          timeout: 3000,
        });
        // 检查二维码是否过期
        const isExpired = await page.evaluate(() => {
          const errorDiv = document.querySelector('div[class*="error-"]');
          return errorDiv && errorDiv.textContent.includes("点击刷新");
        });
        if (isExpired && qrCodeResendCount < maxResendCount) {
          logger.info(
            `二维码已过期，正在重新发送...（第${qrCodeResendCount + 1}次）`
          );
          await sendLoginQRCode(page);
          qrCodeResendCount++;
        }
      } catch (e) {
        clearInterval(checkInterval);
        clearTimeout(timeoutId);
        logger.info("检测到登录成功");
        await notification(
          "抖音",
          `${process.env.NODE_ENV}环境下的抖音登录成功`,
          "登录成功"
        );
        resolve();
      }
    }, 5000); // 每5秒检查一次

    const timeoutId = setTimeout(async () => {
      clearInterval(checkInterval);
      logger.error("登录等待超时（10分钟）");
      await notification(
        "抖音",
        `${process.env.NODE_ENV}环境下的抖音登录失败`,
        "等待超时"
      );
      reject(new Error("登录等待超时，请重试"));
    }, 10 * 60 * 1000);
  });
}

function calculateTimeRange(updateType) {
  const now = dayjs();
  let start_time, end_time;

  switch (updateType) {
    case "highFrequency":
      // 高频率：近3天发布的作品
      start_time = now.subtract(3, "day").startOf("day");
      end_time = now;
      break;
    case "mediumFrequency":
      // 中频率：4~15天内发布作品
      start_time = now.subtract(15, "day").startOf("day");
      end_time = now.subtract(3, "day").endOf("day");
      break;
    case "all":
      // 不限时间
      start_time = now.subtract(100, "year").startOf("day"); // 设置一个足够早的时间
      end_time = now;
      break;
    default:
      throw new Error("Invalid update type");
  }

  return { start_time, end_time };
}

async function scrapeAllPages(page, params) {
  logger.info(
    `开始爬取所有页面，任务类型：${params.update_type || "更新单个视频信息"}${params.video_id ? `，视频ID：${params.video_id}` : ""
    }`
  );
  const { video_id, update_type = "single" } = params;
  let start_time, end_time;

  if (update_type !== "single") {
    ({ start_time, end_time } = calculateTimeRange(update_type));
  } else {
    // 对于单个视频，我们不需要时间范围
    start_time = null;
    end_time = null;
  }

  const result = { data: [] };
  let hasNextPage = true;
  let currentPage = 1;
  let foundRelevantData = false;
  let passedStartTime = false;

  logger.info(
    `开始任务，类型：${update_type}${video_id ? `，视频ID：${video_id}` : ""}`
  );

  while (hasNextPage) {
    logger.info(`正在处理第 ${currentPage} 页数据`);
    const pageData = await getData(page);
    const crawl_time = dayjs().format("YYYY-MM-DD HH:mm:ss");

    const processedData = pageData.items
      .filter((item) => {
        if (video_id) {
          return item?.item_info?.base_info?.item_id === video_id;
        }
        if (update_type === "single") {
          return true; // 如果是单个视频模式但没有指定 video_id，我们获取所有视频
        }
        const publishTime = dayjs(item?.item_info?.base_info?.publish_time);

        if (publishTime.isBefore(start_time)) {
          passedStartTime = true;
        }

        if (publishTime.isAfter(start_time) && publishTime.isBefore(end_time)) {
          foundRelevantData = true;
          return true;
        }

        return false;
      })
      .map((item) => processVideoItem(item, crawl_time));

    if (processedData.length > 0) {
      result.data.push(...processedData);
      logger.info(
        `当前页面找到 ${processedData.length} 条符合条件的数据，总 ${result.data.length} 条`
      );

      if (video_id && result.data.some((item) => item.item_id === video_id)) {
        logger.info(`找到指定的视频 (ID: ${video_id})，停止爬取`);
        break;
      }
    } else {
      logger.info(`当前页面没有找到符合条件的数据`);
    }

    // 判断是否应该继续爬取下一页
    if (update_type !== "single") {
      if (passedStartTime && foundRelevantData) {
        logger.info(
          `已找到目标时间范围内的数据，并且已经通过了开始时间，停止爬取`
        );
        break;
      }

      if (passedStartTime && !foundRelevantData) {
        logger.info(`已经通过了开始时间，但没有找到相关数据，停止爬取`);
        break;
      }
    }

    await delay(4000);
    hasNextPage = await goToNextPage(page);
    if (hasNextPage) {
      currentPage++;
      logger.info(`进入下一页 (第 ${currentPage} 页)`);
    } else {
      logger.info(`已到达最后一页，停止爬取`);
    }
  }

  logger.info(`任务完成，共找到 ${result.data.length} 条数据`);
  return result;
}

function processVideoItem(item, crawl_time) {
  return {
    crawl_time: crawl_time,
    item_id: item?.item_info?.base_info?.item_id || "",
    category: item?.item_info?.base_info?.category || "",
    cover: JSON.stringify(item?.item_info?.base_info?.cover?.url_list || []),
    publish_time: dayjs(item?.item_info?.base_info?.publish_time || "").format(
      "YYYY-MM-DD HH:mm:ss"
    ),
    title: item?.item_info?.base_info?.title || "",
    collect_count: parseInt(item?.item_info?.metrics?.collect_count, 10) || 0,
    comment_count: parseInt(item?.item_info?.metrics?.comment_count, 10) || 0,
    share_count: parseInt(item?.item_info?.metrics?.share_count, 10) || 0,
    play_count: parseInt(item?.item_info?.metrics?.play_count, 10) || 0,
    finish: item?.item_info?.metrics?.finish || 0,
    finish5s: item?.item_info?.metrics?.finish5s || 0,
    increase_fans_count:
      parseInt(item?.item_info?.metrics?.increase_fans_count, 10) || 0,
    like_count: parseInt(item?.item_info?.metrics?.like_count, 10) || 0,
    play_avg_time: item?.item_info?.metrics?.play_avg_time || 0,
    skip2s: item?.item_info?.metrics?.skip2s || 0,
    visit_count: parseInt(item?.item_info?.metrics?.visit_count, 10) || 0,
    video_path: item?.item_info?.base_info?.item_id
      ? `https://www.douyin.com/video/${item?.item_info?.base_info?.item_id}`
      : "",
  };
}

async function goToNextPage(page) {
  const nextButton = await page.$(
    '.douyin-creator-pc-page-next[aria-disabled="false"]'
  );
  if (nextButton) {
    await nextButton.click();
    await page.waitForSelector(".douyin-creator-pc-table-tbody tr");
    await page.waitForSelector('td[aria-colindex="1"] span');
    return true;
  }
  return false;
}

async function getData(page) {
  return new Promise((resolve, reject) => {
    const responseHandler = async (response) => {
      const request = response.request();
      const url = response.url();
      const method = request.method();
      const headers = response.headers();
      const contentEncoding = headers["content-encoding"];

      if (
        url.includes(
          "https://creator.douyin.com/janus/douyin/creator/data/pc/item/list"
        ) &&
        method === "POST" &&
        contentEncoding === "br"
      ) {
        try {
          const text = await response.text();
          const data = JSON.parse(text);
          page.off("response", responseHandler);
          resolve(data);
        } catch (error) {
          reject(error);
        }
      }
    };

    page.on("response", responseHandler);
  });
}

// <--------------------------------------获用户信息和视频信息结束----------------------------------------------->

// <--------------------------------------上传视频开始----------------------------------------------->

async function postVideo(data) {
  logger.info(`抖音发布视频的所有参数：${JSON.stringify(data, null, 2)}`);
  let browser, video_id;
  try {
    // 1. 下载视频和封面文件
    await downloadFiles(data);
    // 2. 启动浏览器
    browser = await puppeteer.launch({
      executablePath,
      headless: false,
      userDataDir,
      args: [
        "--no-sandbox",
        "--disable-setuid-sandbox",
        "--disable-features=site-per-process",
      ],
      ignoreHTTPSErrors: true,
    });
    // 3. 创建新页面登录
    const page = await browser.newPage();
    // 设置页面视口为浏览器窗口的实际尺寸
    await page.setViewport({
      width: 1920,
      height: 1080,
    });
    await page.goto("https://creator.douyin.com/");
    try {
      // #TAG: 登录按钮
      await page.waitForSelector(".douyin-creator-vmock-input-suffix", {
        timeout: 3000,
      });
      logger.error("登录已失效，正在尝试发送登录二维码...");
      await sendLoginQRCode(page);
      await waitForUserLogin(page);
    } catch (e) {
      if (e.name === "TimeoutError") {
        // 如果是超时错误，说明没有找到登录按钮，可能已经登录
        logger.info("未检测到登录按钮，用户已登录");
      } else {
        // 其他类型的错误，可能是发送二维码或登录过程中出现的问题
        logger.error("登录过程中出现错误：", e.message);
        throw e; // 重新抛出错误，让上层函数处理
      }
    }
    // 4.监听视频ID
    page.on("response", async (response) => {
      const request = response.request();
      const url = response.url();
      const method = request.method();
      if (
        url.includes("https://creator.douyin.com/web/api/media/aweme/create/")
      ) {
        try {
          const text = await response.text();
          const data = JSON.parse(text);
          video_id = data.aweme.aweme_id;
        } catch (error) {
          logger.error(`获取视频id失败:${error.message}`);
        }
      }
    });
    // 5.上传内容
    await page.goto("https://creator.douyin.com/creator-micro/content/upload");
    await uploadContent(page, data);

    // 6.检查发布是否成功
    const publishSuccess = await checkPublishSuccess(page);

    if (publishSuccess) {
      logger.info(`发布成功，视频id：${video_id}`);
      return video_id;
    } else {
      throw new Error("发布失败");
    }
  } catch (error) {
    logger.error(error);
    throw new Error("发布失败");
  } finally {
    if (browser) await browser.close();
  }
}
//抖音发布时视频成功回请求
async function publishCallback(id, status, message, task_id, platform_key) {
  logger.info(
    `回调参数：url: ${process.env.WF221_API_BASE_URL}"/auth/api/post/callback",id: ${id}, status: ${status}, message: ${message}, task_id: ${task_id}, platform_key（作品id）: ${platform_key}`
  );
  return await axios.put(
    process.env.WF221_API_BASE_URL + "/auth/api/post/callback",
    {
      id,
      status,
      message,
      task_id,
      platform_key,
    },
    {
      headers: {
        "X-AUTH": "5f228e09-bab0-4d6a-9d23-aed5c2d1db0d",
      },
    }
  );
}

async function downloadFiles(data) {
  logger.info(`开始下载视频，标题：${data.title}`);
  const date = dayjs().format("YYYY-MM-DD");
  const dir = path.join(__dirname, `../uploads/douyin/${date}`);
  if (!fs.existsSync(dir)) {
    fs.mkdirSync(dir, { recursive: true });
  }

  const videoName = path.basename(new URL(data.video).pathname);
  const coverName = path.basename(new URL(data.cover).pathname);

  const videoPath = path.join(dir, videoName);
  const coverPath = path.join(dir, coverName);

  await Promise.all([
    downloadFile(data.video, videoPath),
    downloadFile(data.cover, coverPath),
  ]);

  data.video = videoPath;
  data.cover = coverPath;
  logger.info(`下载成功，视频地址：${videoPath}，图片地址：${coverPath}`);
}

async function downloadFile(fileUrl, localPath) {
  const response = await axios({
    method: "GET",
    url: fileUrl,
    responseType: "stream",
  });

  const writer = fs.createWriteStream(localPath);
  response.data.pipe(writer);

  return new Promise((resolve, reject) => {
    writer.on("finish", resolve);
    writer.on("error", reject);
  });
}

async function checkLogin(page) {
  try {
    // #TAG: 登录按钮
    await page.waitForSelector(".douyin-creator-vmock-input-suffix", {
      timeout: 3000,
    });
    logger.error("登录已失效，请手动登录，然后按 Enter 键继续...");
    await new Promise((resolve) => rl.once("line", resolve));
    rl.close();
    return true;
  } catch (e) {
    logger.info("已登录");
    return true;
  }
}

async function uploadContent(page, data) {
  // 上传视频
  logger.info("开始上传视频内容");
  logger.info("上传视频文件");
  await uploadVideo(page, data.video);

  // 填写标题和描述
  logger.info("填写标题和描述");
  await fillTitleAndDescription(page, data);

  // 上传封面
  logger.info("上传封面");
  await delay(1500);
  await uploadCover(page, data.cover);

  // 设置合集
  await setCollection(page, data.collection);

  // 设置视频权限和发布时间
  await setVideoSettings(page);

  // 点击发布按钮
  //等待类名包含resultWrapper-的section元素出现，并且其内容包含“检测通过”，怎么实现
  await page.waitForSelector('section[class*="resultWrapper-"]', {
    timeout: 1200000,
  });
  logger.info("点击发布按钮");
  await clickPublishButton(page);
}

async function uploadVideo(page, videoPath) {
  uploadInput = await page.waitForSelector(
    "input[type='file'][accept='video/*']"
  );
  await uploadInput.uploadFile(videoPath);
  await delay(3000);
}

async function fillTitleAndDescription(page, data) {
  const titleInput = await page.waitForSelector('input[type="text"]');
  await titleInput.type(data.title);

  const descriptionEditor = 'div.editor[contenteditable="true"]';
  await page.focus(descriptionEditor);
  for (let tag of data.talk) {
    await page.keyboard.type(tag + " ");
  }
  await page.keyboard.type(data.summary);
}

async function uploadCover(page, coverPath) {
  await page.evaluate(() => {
    const coverDivs = [
      ...document.querySelectorAll('div[class*="title-"]'),
    ].filter((div) => div.textContent.includes("选择封面"));

    if (coverDivs.length >= 2) {
      console.log('点击第二个包含 "选择封面" 的元素');
      coverDivs[1].click();
    } else {
      throw new Error("未找到足够的 选择封面选项");
    }
  });
  //2024-09-09 改版
  // await page.evaluate(() => {
  //   const coverDiv = [
  //     ...document.querySelectorAll('div[class*="tabItem-"]'),
  //   ].find((div) => div.textContent.includes("上传封面"));
  //   if (coverDiv) {
  //     console.log('点击包含 "上传封面" 的元素');
  //     coverDiv.click();
  //   } else {
  //     throw new Error("未找到 上传封面选项");
  //   }
  // });
  const coverInput = await page.waitForSelector(
    "input.semi-upload-hidden-input"
  );
  await coverInput.uploadFile(coverPath);
  logger.info("上传封面成功");
  await delay(5000);
  const elements = await page.$$("span.semi-button-content");
  const confirmButton = [];
  for (const element of elements) {
    const text = await element.evaluate((el) => el.textContent);
    if (text.includes("完成")) {
      confirmButton.push(element);
    }
    if (confirmButton.length === 2) break;
  }

  if (confirmButton.length >= 2) {
    const secondElement = confirmButton[1];
    await secondElement.click();
    await delay(5000);
    logger.info("上传封面，点击完成");
  } else if (confirmButton.length === 1) {
    await confirmButton[0].click();
    await delay(5000);
    logger.info("上传封面，点击完成");
  } else {
    logger.error("没找到完成按钮");
  }
}

async function setCollection(page, collection) {
  await page.evaluate((collection) => {
    const collectionDiv = Array.from(document.querySelectorAll("div")).find(
      (div) => div.textContent.trim() === "请选择合集"
    );
    if (collectionDiv) {
      collectionDiv.click();
    }
    setTimeout(() => {
      const collectionList = document.querySelector(".semi-select-option-list");
      const collectionItems = collectionList.querySelectorAll(
        ".semi-select-option span"
      );
      const targetCollection = Array.from(collectionItems).find(
        (item) => item.textContent.trim() === collection
      );

      if (targetCollection) {
        targetCollection.click();
      } else {
        collectionList.querySelector(".semi-select-option").click();
      }
    }, 1000);
  }, collection);
  await delay(3000);
}

async function setVideoSettings(page) {
  // 设置允许他人保存视频
  const saveSwitches = await page.$$('[class*="download-content-"]');
  const saveSwitch = saveSwitches[0]; // 选择第一个匹配的元素
  // 使用元素句柄的 $ 方法查找子元素
  const saveSwitchLabel = await saveSwitch.$("label");
  if (saveSwitchLabel) {
    await saveSwitchLabel.click();
  } else {
    logger.error("未找到允许他人保存视频的label");
  }
  // 设置谁可以看
  const publishSettingsLabels = await page.$$('[class*="publish-settings"]');
  const publishSettingsLabel = publishSettingsLabels[0]; // 选择第一个匹配的元素
  const publishSettingsLabelElement = await publishSettingsLabel.$("label");

  if (publishSettingsLabelElement) {
    await publishSettingsLabelElement.click();
  } else {
    logger.error("未找到谁可以看的label");
  }

  // 设置发布时间
  const scheduleLabels = await page.$$('[class*="publish-settings"]');
  const scheduleLabel = scheduleLabels[0]; // 选择第一个匹配的元素
  const scheduleLabelElement = await scheduleLabel.$("label");

  if (scheduleLabelElement) {
    await scheduleLabelElement.click();
  } else {
    logger.error("未找到发布时间的label");
  }
}

async function clickPublishButton(page) {
  const publishButton = await page.waitForSelector(
    'div[class*="content-confirm-container"] button'
  );
  await publishButton.click();
  await delay(10000);
}

async function checkPublishSuccess(page) {
  return page.evaluate(() => {
    return window.location.href.includes(
      "https://creator.douyin.com/creator-micro/content/manage"
    );
  });
}
// <--------------------------------------上传视频结束----------------------------------------------->

// <--------------------------------------获取账号下所有视频信息开始（openApi）----------------------------------------------->
//通过openApi获取抖音视频列表
async function getDouyinVideosByOpenApi(count = 20, cursor = 0) {
  try {
    const response = await axios.get(
      `https://open.douyin.com/api/douyin/v1/video/video_list/`,
      {
        headers: {
          "access-token": process.env.DOUYIN_OPENAPI_ACCESS_TOKEN,
        },
        params: {
          open_id: process.env.DOUYIN_OPENAPI_OPEN_ID,
          count,
          cursor,
        },
      }
    );
    return response.data;
  } catch (error) {
    throw error;
  }
}
async function getVideos(params) {
  logger.info(
    `开始通过 OpenAPI 获取视频列表，时间范围：${params.start_time} 到 ${params.end_time}`
  );
  let count = 20,
    page = 0,
    cur_cursor = 0;
  const { start_time, end_time } = params;
  const startTime = dayjs(start_time);
  const endTime = dayjs(end_time);
  if (!startTime.isValid() || !endTime.isValid()) {
    throw new Error("start_time,end_time 参数错误");
  }
  let allVideos = [];
  let hasMore = true;
  let stopFetching = false;
  while (hasMore) {
    page++;
    logger.info(`正在获取第${page}页数据`);
    const response = await getDouyinVideosByOpenApi(count, cur_cursor);
    await delay(1000);
    const { list, has_more, cursor } = response.data;
    if (Array.isArray(list) && list.length > 0) {
      for (const video of list) {
        if (
          video.video_status !== 2 &&
          video.title !== "" &&
          video.create_time !== 0
        ) {
          const videoTime = dayjs(video.create_time * 1000);
          if (videoTime.isAfter(startTime) && videoTime.isBefore(endTime)) {
            allVideos.push({
              item_id: video.video_id,
              cover: JSON.stringify([video.cover]),
              publish_time: dayjs(video.create_time * 1000).format(
                "YYYY-MM-DD HH:mm:ss"
              ),
              crawl_time: dayjs().format("YYYY-MM-DD HH:mm:ss"),
              title: video.title,
              video_path: video.video_id
                ? `https://www.douyin.com/video/${video.video_id}`
                : "",
              comment_count: video?.statistics?.comment_count || 0,
              share_count: video?.statistics?.share_count || 0,
              like_count: video?.statistics?.digg_count || 0,
              play_count: video?.statistics?.play_count || 0,
            });
          } else {
            // 如果遇到不符合时间区间的视频，停止请求
            logger.error(
              `视频${video.title}不符合时间范围，跳过, ${JSON.stringify(
                video,
                null,
                2
              )}`
            );
            stopFetching = true;
            break;
          }
        } else {
          logger.error(
            `视频${video.title}不符合时间范围，跳过, ${JSON.stringify(
              video,
              null,
              2
            )}`
          );
        }
      }
    }
    if (!has_more || stopFetching) {
      hasMore = false;
    } else {
      cur_cursor = cursor;
    }
  }
  return allVideos;
}
//<--------------------------------------获取账号下所有视频信息结束（openApi）----------------------------------------------->

exports.handleCommand = async (command, params, taskId) => {
  switch (command) {
    case "getUserInfo":
      try {
        logger.info(`⭐️⭐️⭐️⭐️执行获取用户信息命令，关键字：${params?.keyword}`);
        const user = await getUserInfo(params?.keyword);
        return { data: [user] };
      } catch (error) {
        throw error;
      }
    case "getVideosInfo":
      try {
        logger.info(`执行获取视频信息命令，参数：${JSON.stringify(params)}`);
        const videos = await getVideosInfo(params || {});
        if (videos.data.length > 0) {

          saveDouyin2Mysql(videos)
        }
        return videos;
      } catch (error) {
        logger.error(error.message);
        throw error;
      }
    case "postVideo":
      try {
        logger.info(`执行发布视频命令，任务ID：${taskId}`);
        const result = await postVideo(params);
        /*
          const params = {
            title: "这是一个测试视频",
            video: "https://example.com/video.mp4",
            cover: "https://example.com/cover.jpg",
            talk: ["#抖音", "#测试视频", "#生活记录"],
            summary: "这是一个测试视频的详细描述，分享生活的美好时刻。",
            collection: "日常生活",
            post_id: "post_123456"
          };
        */
        //await publishCallback(params.post_id, 3, "发布成功", taskId, result);
        return {
          data: [
            {
              video_id: result,
            },
          ],
        };
      } catch (error) {
        //await publishCallback(params.post_id, 4, error.message, taskId, "");
        logger.error(error.message);
        throw error;
      }
    case "getVideos":
      try {
        logger.info(`执行获取视频列表命令，参数：${JSON.stringify(params)}`);
        const result = await getVideos(params);
        return {
          data: result,
        };
      } catch (error) {
        logger.error(error.message);
        throw error;
      }
    default:
      logger.error(`不支持的命令：${command}`);
      throw new Error("NOT_SUPPORT_COMMAND");
  }
};

// 抖音数据插入mysql 逻辑
const saveDouyin2Mysql = async (videos) => {


  try {
    for (const video of videos.data) {
      // 获取单个视频的详细信息

      const res = await axios.get(
        `http://127.0.0.1:3003/api/douyin/web/fetch_one_video?aweme_id=` + video.video_path.split('/').pop()
      );


      if (!res.data?.data?.aweme_detail) {
        logger.error(`获取视频 ${video.item_id} 详情失败`);
        continue;
      }
      // console.log(res.data.data)
      const awemeDetail = res.data.data.aweme_detail;
      // console.log(res.data.data.aweme_detail.video?.play_addr?.url_list)
      // return []
      // 上传将res中的视频上传七牛云 ⭐️ 这里容易出现bug 要随时换url_list[0] 中的哪个
      const videoUrl = await uploadVideoToQiniu(awemeDetail.video?.play_addr?.url_list[0], 'douyin')
      // http://sm75d8jmh.hn-bkt.clouddn.com/videos/douyin/1730344030370_4slv12.mp4
      const saveData = {
        // 平台标识，固定为"douyin"
        platform: "douyin",
        // 视频唯一标识ID
        aweme_id: awemeDetail.aweme_id,
        // 视频创建时间
        create_time: awemeDetail.create_time,
        // 数据爬取时间，格式：YYYY-MM-DD HH:mm:ss
        crawl_time: dayjs().format('YYYY-MM-DD HH:mm:ss'),

        // 作者UID，用户的唯一数字标识
        author_uid: awemeDetail.author?.uid,
        // 作者加密ID，用于构建用户主页URL
        author_sec_uid: awemeDetail.author?.sec_uid,
        // 作者昵称
        author_nickname: awemeDetail.author?.nickname,
        // 作者个人签名
        author_signature: awemeDetail.author?.signature,
        // 作者头像URL
        author_avatar_url: awemeDetail.author?.avatar_thumb?.url_list[0],
        // 作者关注数量
        author_following_count: awemeDetail.author?.following_count,
        // 作者粉丝数量
        author_follower_count: awemeDetail.author?.follower_count,
        // 作者获赞总数
        author_total_favorited: awemeDetail.author?.total_favorited,
        // 作者抖音号
        author_unique_id: awemeDetail.author?.unique_id,

        // 视频描述/标题
        video_desc: awemeDetail.desc,
        // 视频播放地址
        video_url: 'http://' + videoUrl,
        // 视频封面图片地址
        cover_url: awemeDetail.video?.cover?.url_list[0],
        // 视频时长（毫秒）
        duration: awemeDetail.video?.duration,
        // 视频宽度（像素）
        width: awemeDetail.video?.width,
        // 视频高度（像素）
        height: awemeDetail.video?.height,

        // 评论数量
        comment_count: awemeDetail.statistics?.comment_count,
        // 点赞数量
        digg_count: awemeDetail.statistics?.digg_count,
        // 下载数量
        download_count: awemeDetail.statistics?.download_count,
        // 转发数量
        forward_count: awemeDetail.statistics?.forward_count,
        // 播放数量
        play_count: awemeDetail.statistics?.play_count,
        // 分享数量
        share_count: awemeDetail.statistics?.share_count,

        // 背景音乐ID
        music_id: awemeDetail.music?.id,
        // 背景音乐标题
        music_title: awemeDetail.music?.title,
        // 背景音乐作者
        music_author: awemeDetail.music?.author,
        // 背景音乐时长
        music_duration: awemeDetail.music?.duration,

        // 视频包含的话题标签，JSON字符串格式
        // 包含话题名称(hashtag_name)和话题ID(hashtag_id)
        hashtags: JSON.stringify(awemeDetail.text_extra?.map(tag => ({
          hashtag_name: tag.hashtag_name,
          hashtag_id: tag.hashtag_id
        })) || [])
      };

      //logger.info(`正在保存视频 ${video.item_id} 的数据`);
      await saveMysql(saveData);

      // 添加延迟避免请求过快
      await delay(1000);
    }

    logger.info(`数据保存完成，共处理 ${videos.data.length} 条记录`);
  } catch (error) {
    logger.error(`保存数据时发生错误: ${error.message}`);
    throw error;
  }
}